home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
HPAVC
/
HPAVC CD-ROM.iso
/
ASM_TUTR.ZIP
/
ASMTUTR2.DOC
< prev
next >
Wrap
Text File
|
1984-11-27
|
25KB
|
429 lines
so. An exception is the saving and restoring of registers at
entrance to and exit from a subroutine; here, if the subroutine is
long, you should probably PUSH everything which the caller may need
saved, whether you will use the register or not, and POP it in
reverse order at the end.
Be aware that CALL and INT push return address information on the
stack and RET and IRET pop it off. It is a good idea to become
familiar with the structure of the stack.
c. In practice, to invoke system services you will use the INT
instruction. It is quite possible to use this instruction effec-
tively in a cookbook fashion without knowing precisely how it
works.
d. The transfer of control instructions (CALL, RET, JMP) deserve care-
ful study to avoid confusion. You will learn that these can be
classified as follows:
1) all three have the capability of being either NEAR (CS register
unchanged) or FAR (CS register changed)
2) JMPs and CALLs can be DIRECT (target is assembled into instruc-
tion) or INDIRECT (target fetched from memory or register)
3) if NEAR and DIRECT, a JMP can be SHORT (less than 128 bytes
away) or LONG
In general, the third issue is not worth worrying about. On a for-
ward jump which is clearly VERY short, you can tell the assembler
it is short and save one byte of code:
JMP SHORT CLOSEBY
On a backward jump, the assembler can figure it out for you. On a
forward jump of dubious length, let the assembler default to a LONG
form; at worst you waste one byte.
Also leave the assembler to worry about how the target address is
to be represented, in absolute form or relative form.
e. The conditional jump set is rather confusing when studied apart
from the assembler, but you do need to get a feeling for it. The
interactions of the sign, carry, and overflow flags can get your
mind stuttering pretty fast if you worry about it too much. What
is boils down to, though, is
JZ means what it says
JNZ means what it says
JG reater this means "if the SIGNED difference is positive"
JA bove this means "if the UNSIGNED difference is positive"
JL ess this means "if the SIGNED difference is negative"
JB elow this means "if the UNSIGNED difference is negative"
JC arry assembles the same as JB; it's an aesthetic choice
IBM PC Assembly Language Tutorial 10
You should understand that all conditional jumps are inherently
DIRECT, NEAR, and "short"; the "short" part means that they can't
go more than 128 bytes in either direction. Again, this is some-
thing you could easily imagine to be more of a problem than it is.
I follow this simple approach:
1) When taking an abnormal exit from a block of code, I always use
an unconditional jump. Who knows how far you are going to end
up jumping by the time the program is finished. For example, I
wouldn't code this:
TEST AL,IDIBIT ;Is the idiot bit on?
JNZ OYVEY ;Yes. Go to general cleanup
Rather, I would probably code this:
TEST AL,IDIBIT ;Is the idiot bit on?
JZ NOIDIOCY ;No. I am saved.
JMP OYVEY ;Yes. What can we say...
NOIDIOCY:
The latter, of course, is a jump around a jump. Some would say
it is evil, but I submit it is hard to avoid in this language.
2) Otherwise, within a block of code, I use conditional jumps
freely. If the block eventually grows so long that the assem-
bler starts complaining that my conditional jumps are too long
I
a) consider reorganizing the block but
b) also consider changing some conditional jumps to their
opposite and use the "jump around a jump" approach as shown
above.
Enough about specific instructions!
6. Finally, in order to use the assembler effectively, you need to know
the default rules for which segment registers are used to complete
addresses in which situations.
a. CS is used to complete an address which is the target of a NEAR
DIRECT jump. On an NEAR INDIRECT jump, DS is used to fetch the
address from memory but then CS is used to complete the address
thus fetched. On FAR jumps, of course, CS is itself altered. The
instruction counter is always implicitly pointing in the code seg-
ment.
b. SS is used to complete an address if BP is used in its formation.
Otherwise, DS is always used to complete a data address.
c. On the string instructions, the target is always formed from ES and
DI. The source is normally formed from DS and SI. If there is a
segment prefix, it overrides the source not the target.
IBM PC Assembly Language Tutorial 11
Learning about DOS
__________________
Learning about DOS
Learning about DOS
Learning about DOS
I think the best way to learn about DOS internals is to read the technical
appendices in the manual. These are not as complete as we might wish, but
they really aren't bad; I certainly have learned a lot from them. What you
don't learn from them you might eventually learn via judicious disassembly
of parts of DOS, but that shouldn't really be necessary.
From reading the technical appendices, you learn that interrupts 20H
through 27H are used to communicate with DOS. Mostly, you will use inter-
rupt 21H, the DOS function manager.
The function manager implements a great many services. You request the
individual services by means of a function code in the AH register. For
example, by putting a nine in the AH register and issuing interrupt 21H you
tell DOS to print a message on the console screen.
Usually, but by no means always, the DX register is used to pass data for
the service being requested. For example, on the print message service
just mentioned, you would put the 16 bit address of the message in the DX
register. The DS register is also implicitly part of this argument, in
keeping with the universal segmentation rules.
In understanding DOS functions, it is useful to understand some history and
also some of the philosophy of MS-DOS with regard to portability. General-
ly, you will find, once you read the technical information on DOS and also
the IBM technical reference, you will know more than one way to do almost
anything. Which is best? For example, to do asynch adapter I/O, you can
use the DOS calls (pretty incomplete), you can use BIOS, or you can go
directly to the hardware. The same thing is true for most of the other
primitive I/O (keyboard or screen) although DOS is more likely to give you
added value in these areas. When it comes to file I/O, DOS itself offers
more than one interface. For example, there are four calls which read data
from a file.
The way to decide rationally among these alternatives is by understanding
the tradeoffs of functionality versus portability. Three kinds of porta-
bility need to be considered: machine portability, operating system porta-
bility (for example, the ability to assemble and run code under CP/M 86)
and DOS version portability (the ability for a program to run under older
versions of DOS>.
Most of the functions originally offered in DOS 1.0 were direct descendents
of CP/M functions; there is even a compatibility interface so that programs
which have been translated instruction for instruction from 8080 assembler
to 8086 assembler might have a reasonable chance of running if they use
only the core CP/M function set. Among the most generally useful in this
original compatibility set are
IBM PC Assembly Language Tutorial 12
09 -- print a full message on the screen
0A -- get a console input line with full DOS editing
0F -- open a file
10 -- close a file (really needed only when writing)
11 -- find first file matching a pattern
12 -- find next file matching a pattern
13 -- erase a file
16 -- create a file
17 -- rename a file
1A -- set disk transfer address
The next set provide no function above what you can get with BIOS calls or
more specialized DOS calls. However, they are preferable to BIOS calls
when portability is an issue.
00 -- terminate execution
01 -- read keyboard character
02 -- write screen character
03 -- read COM port character
04 -- write COM port character
05 -- print a character
06 -- read keyboard or write screen with no editing
The standard file I/O calls are inferior to the specialized DOS calls but
have the advantage of making the program easier to port to CP/M style sys-
tems. Thus they are worth mentioning:
14 -- sequential read from file
15 -- sequential write to file
21 -- random read from file
22 -- random write to file
23 -- determine file size
24 -- set random record
In addition to the CP/M compatible services, DOS also offers some special-
ized services which have been available in all releases of DOS. These
include
27 -- multi-record random read.
28 -- multi-record random write.
29 -- parse filename
2A-2D -- get and set date and time
All of the calls mentioned above which have anything to do with files make
use of a data area called the "FILE CONTROL BLOCK" (FCB). The FCB is any-
where from 33 to 37 bytes long depending on how it is used. You are
responsible for creating an FCB and filling in the first 12 bytes, which
contain a drive code, a file name, and an extension.
When you open the FCB, the system fills in the next 20 bytes, which
includes a logical record length. The initial lrecl is always 128 bytes,
to achieve CP/M compatibility. The system also provides other useful
information such as the file size.
IBM PC Assembly Language Tutorial 13
After you have opened the FCB, you can change the logical record length.
If you do this, your program is no longer CP/M compatible, but that doesn't
make it a bad thing to do. DOS documentation suggests you use a logical
record length of one for maximum flexibility. This is usually a good
recommendation.
To perform actual I/O to a file, you eventually need to fill in byte 33 or
possibly bytes 34-37 of the FCB. Here you supply information about the
record you are interested in reading or writing. For the most part, this
part of the interface is compatible with CP/M.
In general, you do not need to (and should not) modify other parts of the
FCB.
The FCB is pretty well described in appendix E of the DOS manual.
Beginning with DOS 2.0, there is a whole new system of calls for managing
files which don't require that you build an FCB at all. These calls are
quite incompatible with CP/M and also mean that your program cannot run
under older releases of DOS. However, these calls are very nice and easy
to use. They have these characteristics
1. To open, create, delete, or rename a file, you need only a character
string representing its name.
2. The open and create calls return a 16 bit value which is simply placed
in the BX register on subsequent calls to refer to the file.
3. There is not a separate call required to specify the data buffer.
4. Any number of bytes can be transfered on a single call; no data area
must be manipulated to do this.
The "new" DOS calls also include comprehensive functions to manipulate the
new chained directory structure and to allocate and free memory.
Learning the assembler
______________________
Learning the assembler
Learning the assembler
Learning the assembler
It is my feeling that many people can teach themselves to use the assembler
by reading the MACRO Assembler manual if
1. You have read and understood a book like Morse and thus have a feeling
for the instruction set
2. You know something about DOS services and so can communicate with the
keyboard and screen and do something marginally useful with files. In
the absence of this kind of knowledge, you can't write meaningful prac-
tice programs and so will not progress.
3. You have access to some good examples (the ones supplied with the
assembler are not good, in my opinion. I will try to supply you with
some more relevant ones.
IBM PC Assembly Language Tutorial 14
4. You ignore the things which are most confusing and least useful. Some
of the most confusing aspects of the assembler include the facilities
combining segments. But, you can avoid using all but the simplest of
these facilities in many cases, even while writing quite substantial
applications.
5. The easiest kind of assembler program to write is a COM program. They
might seem harder, at first, then EXE programs because there is an
extra step involved in creating the executable file, but COM programs
are structurally very much simpler.
At this point, it is necessary to talk about COM programs and EXE programs.
As you probably know, DOS supports two kinds of executable files. EXE pro-
grams are much more general, can contain many segments, and are generally
built by compilers and sometimes by the assembler. If you follow the lead
given by the samples distributed with the assembler, you will end up with
EXE programs. A COM program, in contrast, always contains just one
segment, and receives control with all four segment registers containing
the same value. A COM program, thus, executes in a simplified environment,
a 64K address space. You can go outside this address space simply by tem-
porarily changing one segment register, but you don't have to, and that is
the thing which makes COM programs nice and simple. Let's look at a very
simple one.
The classic text on writing programs for the C language says that the first
thing you should write is a program which says
HELLO, WORLD.
when invoked. What's sauce for C is sauce for assembler, so let's start
with a HELLO program of our own. My first presentation of this will be
bare bones, not stylistically complete, but just an illustration of what an
assembler program absolutely has to have:
HELLO SEGMENT ;Set up HELLO code and data section
ASSUME CS:HELLO,DS:HELLO ;Tell assembler about conditions at entry
ORG 100H ;A .COM program begins with 100H byte prefix
MAIN: JMP BEGIN ;Control must start here
MSG DB 'Hello, world.$' ;But it is generally useful to put data first
BEGIN: MOV DX,OFFSET MSG ;Let DX --> message.
MOV AH,9 ;Set DOS function code for printing a message
INT 21H ;Invoke DOS
RET ;Return to system
HELLO ENDS ;End of code and data section
END MAIN ;Terminate assembler and specify entry point
First, let's attend to some obvious points. The macro assembler uses the
general form
name opcode operands
Unlike the 370 assembler, though, comments are NOT set off from operands by
blanks. The syntax uses blanks as delimiters within the operand field (see
line 6 of the example) and so all comments must be set off by semi-colons.
IBM PC Assembly Language Tutorial 15
Line comments are frequently set off with a semi-colon in column 1. I use
this approach for block comments too, although there is a COMMENT statement
which can be used to introduce a block comment.
Being an old 370 type, I like to see assembler code in upper case, although
my comments are mixed case. Actually, the assembler is quite happy with
mixed case anywhere.
As with any assembler, the core of the opcode set consists of opcodes which
generate machine instructions but there are also opcodes which generate
data and ones which function as instructions to the assembler itself, some-
times called pseudo-ops. In the example, there are five lines which gener-
ate machine code (JMP, MOV, MOV, INT, RET), one line which generates data
(DB) and five pseudo-ops (SEGMENT, ASSUME, ORG, ENDS, and END).
We will discuss all of them.
Now, about labels. You will see that some labels in the example end in a
colon and some don't. This is just a bit confusing at first, but no real
mystery. If a label is attached to a piece of code (as opposed to data),
then the assembler needs to know what to do when you JMP to or CALL that
label. By convention, if the label ends in a colon, the assembler will use
the NEAR form of JMP or CALL. If the label does not end in a colon, it
will use the FAR form. In practice, you will always use the colon on any
label you are jumping to inside your program because such jumps are always
NEAR; there is no reason to use a FAR jump within a single code section. I
mention this, though, because leaving off the colon isn't usually trapped
as a syntax error, it will generally cause something more abstruse to go
wrong.
On the other hand, a label attached to a piece of data or a pseudo-op never
ends in a colon.
Machine instructions will generally take zero, one or two operands. Where
there are two operands, the one which receives the result goes on the left
as in 370 assembler.
I tried to explain this before, now maybe it will be even clearer: there
are many more 8086 machine opcodes then there are assembler opcodes to rep-
resent them. For example, there are five kinds of JMP, four kinds of CALL,
two kinds of RET, and at least five kinds of MOV depending on how you count
them. The macro assembler makes a lot of decisions for you based on the
form taken by the operands or on attributes assigned to symbols elsewhere
in your program. In the example above, the assembler will generate the
NEAR DIRECT form of JMP because the target label BEGIN labels a piece of
code instead of a piece of data (this makes the JMP DIRECT) and ends in a
colon (this makes the JMP NEAR). The assembler will generate the immediate
forms of MOV because the form OFFSET MSG refers to immediate data and
because 9 is a constant. The assembler will generate the NEAR form of RET
because that is the default and you have not told it otherwise.
The DB (define byte) pseudo-op is an easy one: it is used to put one or
more bytes of data into storage. There is also a DW (define word)
pseudo-op and a DD (define doubleword) pseudo-op; in the PC MACRO assem-
bler, the fact that a label refers to a byte of storage, a word of storage,
IBM PC Assembly Language Tutorial 16
or a doubleword of storage can be very significant in ways which we will
see presently.
About that OFFSET operator, I guess this is the best way to make the point
about how the assembler decides what instruction to assemble: an analogy
with 370 assembler:
PLACE DC ......
...
LA R1,PLACE
L R1,PLACE
In 370 assembler, the first instruction puts the address of label PLACE in
register 1, the second instruction puts the contents of storage at label
PLACE in register 1. Notice that two different opcodes are used. In the
PC assembler, the analogous instructions would be
PLACE DW ......
...
MOV DX,OFFSET PLACE
MOV DX,PLACE
If PLACE is the label of a word of storage, then the second instruction
will be understood as a desire to fetch that data into DX. If X is a
label, then "OFFSET X" means "the ordinary number which represents X's off-
set from the start of the segment." And, if the assembler sees an ordinary
number, as opposed to a label, it uses the instruction which is equivalent
to LA.
If PLACE were the label of a DB pseudo-op, instead of a DW, then
MOV DX,PLACE
would be illegal. The assembler worries about length attributes of its
operands.
Next, numbers and constants in general. The assembler's default radix is
decimal. You can change this, but I don't recommend it. If you want to
represent numbers in other forms of notation such as hex or bit, you gener-
ally use a trailing letter. For example,
21H
is hexidecimal 21,
00010000B
is the eight bit binary number pictured.
The next elements we should point to are the SEGMENT...ENDS pair and the
END instruction. Every assembler program has to have these elements.
SEGMENT tells the assembler you are starting a section of contiguous mate-
rial (code and/or data). The symmetrically named ENDS statement tells the
assembler you are finished with a section of contiguous material. I wish
they didn't use the word SEGMENT in this context. To me, a "segment" is a
hardware construct: it is the 64K of real storage which becomes address-
able by virtue of having a particular value in a segment register. Now, it
IBM PC Assembly Language Tutorial 17
is true that the "segments" you make with the assembler often correspond to
real hardware "segments" at execution time. But, if you look at things
like the GROUP and CLASS options supported by the linker, you will discover
that this correspondence is by no means exact. So, at risk of maybe con-
fusing you even more, I am going to use the more informal term "section" to
refer to the area set off by means of the SEGMENT and ENDS instructions.
The sections delimited by SEGMENT...ENDS pairs are really a lot like CSECTs
and DSECTs in the 370 world.
I strongly recommend that you be selective in your study of the SEGMENT
pseudo-op as described in the manual. Let me just touch on it here.
name SEGMENT
name SEGMENT PUBLIC
name SEGMENT AT nnn
Basically, you can get away with just the three forms given above. The
first form is what you use when you are writing a single section of assem-
bler code which will not be combined with other pieces of code at link
time. The second form says that this assembly only contains part of the
section; other parts might be assembled separately and combined later by
the linker.
I have found that one can construct reasonably large modular applications
in assembler by simply making every assembly use the same segment name and
declaring the name to be PUBLIC each time. If you read the assembler and
linker documentation, you will also be bombarded by information about more
complex options such as the GROUP statement and the use of other "combine
types" and "classes." I don't recommend getting into any of that. I will
talk more about the linker and modular construction of programs a little
later. The assembler manual also implies that a STACK segment is required.
This is not really true. There are numerous ways to assure that you have a
valid stack at execution time.
Of course, if you plan to write applications in assembler which are more
than 64K in size, you will need more than what I have told you; but who is
really going to do that? Any application that large is likely to be coded
in a higher level language.
The third form of the SEGMENT statement makes the delineated section into
something like a "DSECT;" that is, it doesn't generate any code, it just
describes what is present somewhere already in the computer's memory.
Sometimes the AT value you give is meaningful. For example, the BIOS work
area is located at location 40 hex. So, you might see
BIOSAREA SEGMENT AT 40H ;Map BIOS work area
ORG BIOSAREA+10H
EQUIP DB ? ;Location of equipment flags, first byte
BIOSAREA ENDS
in a program which was interested in mucking around in the BIOS work area.
At other times, the AT value you give may be arbitrary, as when you are
mapping a repeated control block:
IBM PC Assembly Language Tutorial 18